package upnp

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"
)

const (
	soapNSURL       = "http://schemas.xmlsoap.org/soap/envelope/"
	soapEncodingURL = "http://schemas.xmlsoap.org/soap/encoding/"
)

var (
	soapHeaderName = xml.Name{Local: "s:Envelope"}
	soapBodyName   = xml.Name{Local: "s:Body"}
)

// Action action interface
type Action interface {
	Name() string
}

type actionData struct {
	name string
	srv  *Service

	// request arguments
	args []string
	// response keys
	resps []string
}

// Name get action name
func (a *actionData) Name() string {
	return a.name
}

func (a *actionData) soapHeader() string {
	return `"` + a.srv.stype + "#" + a.name + `"`
}

func (a *actionData) soapType() string {
	return a.srv.stype
}

func (a *actionData) sendRequest(body io.Reader, n int) (*http.Response, error) {
	req, err := http.NewRequest("POST", a.srv.ctl, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("SOAPAction", a.soapHeader())
	req.Header.Set("Content-Type", `text/xml; charset="utf-8"`)
	req.Header.Set("Content-Length", fmt.Sprintf("%d", n))
	cli := &http.Client{Timeout: 10 * time.Second}
	return cli.Do(req)
}

type actionBodyType struct {
	XMLName  xml.Name
	NS       string `xml:"xmlns:s,attr"`
	Encoding string `xml:"s:encodingStyle,attr"`
	Body     struct {
		XMLName xml.Name
		Action  interface{}
	}
}

type actionBodyContentType interface {
	Init(actionData)
	Initialized() bool
}

func newActionBody(action interface{}) (*actionBodyType, error) {
	if a, ok := action.(actionBodyContentType); ok {
		if !a.Initialized() {
			return nil, errors.New("body is not initialized")
		}
		body := new(actionBodyType)
		body.XMLName = soapHeaderName
		body.NS = soapNSURL
		body.Encoding = soapEncodingURL
		body.Body.XMLName = soapBodyName
		body.Body.Action = action
		return body, nil
	}
	return nil, errors.New("body is not a valid actionBodyContentType")
}

// ActionBodyContent body in request body, parent struct of actionBodyType.Body.Action
type ActionBodyContent struct {
	XMLName xml.Name
	U       string `xml:"xmlns:u,attr"`
	init    bool
}

// Init initialize
func (b *ActionBodyContent) Init(a actionData) {
	b.XMLName = xml.Name{Local: "u:" + a.Name()}
	b.U = a.soapType()
	b.init = true
}

// Initialized return is initialized
func (b *ActionBodyContent) Initialized() bool {
	return b.init
}

// ActionResponse action caller response
type ActionResponse struct {
	// Code=0 means ok, otherwise error, error message in Msg field
	Code int
	Msg  string
	// response data different of each action
	Data interface{}
}
